package org.spbsu.apco.server.monitoring.event;

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spbsu.apco.common.monitoring.event.ServiceEvent;
import org.spbsu.apco.common.monitoring.event.TaskEvent;
import org.spbsu.apco.common.serialization.Serializer;
import org.spbsu.apco.common.utils.NetworkUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.PathSegment;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * User: mikpan
 * Date: 12/12/12
 * Time: 11:51 AM
 */
@Service(value = "ServiceInvocationFilter")
@Qualifier("ServiceInvocationFilter")
public class ServiceInvocationFilter implements Filter {
    private static final Logger log = LoggerFactory.getLogger(ServiceInvocationFilter.class);

    @Resource
    MonitorService monitorService;

    @Resource(name = "serializer")
    private Serializer serializer;


    private void monitorEvent(ContainerRequest request) {
        List<PathSegment> pathSegments = request.getPathSegments();
        ServiceEvent event = new ServiceEvent();
        event.setTaskStartTime(System.currentTimeMillis());
        event.setMacAddress(pathSegments.get(2).getPath());

        monitorService.addEvent(event);
    }

    private boolean canBeMonitored(ContainerRequest request) {
        return request.getPathSegments().size()>=3;
    }

    public ContainerRequest filter(ContainerRequest request) {
        if (!canBeMonitored(request)) {
            return request;
        }
        monitorEvent(request);
        return request;
    }

    public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
        if (!canBeMonitored(request)) {
             return response;
        }
        monitorEvent(request);
        return response;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        Request jettyRequest = (Request) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        log.info("Request class implementation in {}", request.getClass().getName());
        log.info("ServiceInvocationFilter request path: {}", jettyRequest.getPathInfo());

        String macAddress = jettyRequest.getHeader(NetworkUtils.X_MAC_SRC);
        log.info("Request from: {}", macAddress);

        ArrayList<String> pathSegmentsList = new ArrayList<String>(Arrays.asList(jettyRequest.getPathInfo().split("/")));

        ServiceEvent event = addEvent(pathSegmentsList, macAddress);
        chain.doFilter(request, response);

        String methodName = pathSegmentsList.get(2);
        if ("holdresult".equals(methodName)) {
            String taskEventString = jettyRequest.getHeader(NetworkUtils.X_EVENT);
            List<TaskEvent> taskEvents = serializer.decodeTaskEvents(taskEventString.getBytes());
            addTaskEvent(taskEvents.get(0));
        } else if ("gettask".equals(methodName)) {
            HttpConnection conn = jettyRequest.getConnection();
            HttpFields fields = conn.getResponseFields();
            String taskEventString = fields.getStringField(NetworkUtils.X_EVENT);
            httpResponse.setHeader(NetworkUtils.X_EVENT, NetworkUtils.X_NULL_VALUE);
            List<TaskEvent> taskEvents = serializer.decodeTaskEvents(taskEventString.getBytes());
            if (taskEvents.size() > 1) {
                // batch task => add all subtasks
                addBatchEvents(taskEvents, event);
            } else {
                // simple task
                String taskId = taskEvents.get(0).getTaskId();
                event.setTaskId(taskId);
            }
        }
    }

    private void addBatchEvents(List<TaskEvent> taskEvents, ServiceEvent parentEvent) {

        boolean listHead = true;
        for (TaskEvent event : taskEvents) {
            // set appropriate taskId

            if (listHead) {
                listHead = false;
                parentEvent.setTaskId(event.getTaskId());
                continue;
            }
            ServiceEvent serviceEvent;
            try {
                serviceEvent = (ServiceEvent) parentEvent.clone();
                serviceEvent.setTaskId(event.getTaskId());
                monitorService.addEvent(serviceEvent);
            } catch (CloneNotSupportedException e) {
            }
        }
    }

    private void addTaskEvent(TaskEvent taskEvent) {
        monitorService.addTaskEvent(taskEvent);
    }

    private ServiceEvent addEvent(ArrayList<String> pathSegments, String macAddress) {
        if (pathSegments == null || pathSegments.size() < 4) {
            return null;
        }
        String serviceName = pathSegments.get(1);
        if (!"task".equals(serviceName)) {
            return null;
        }

        String methodName = pathSegments.get(2);
        if ("holdresult".equals(methodName)) {
            // find dat task and update duration

            String taskId = pathSegments.get(4);
            ServiceEvent event = monitorService.getEventByTaskId(taskId);
            if (event == null) {
                throw new IllegalStateException("Cannot find event by id!");
            }
            long taskStartTime = event.getTaskStartTime();
            event.setTaskDuration(System.currentTimeMillis() - taskStartTime);
            return event;
        }

        // gettask
        ServiceEvent event = new ServiceEvent();
        event.setTaskStartTime(System.currentTimeMillis());
        event.setTaskDuration(-1);
        event.setMacAddress(macAddress);
        event.setOriginatorId(pathSegments.get(3));

        monitorService.addEvent(event);
        return event;
    }

    @Override
    public void destroy() {

    }
}

